/**
 * Disclaimer: modules in _shims aren't intended to be imported by SDK users.
 */
import * as nf from "npm:node-fetch";
import * as fd from "npm:formdata-node";
import { type File, type FilePropertyBag } from "npm:formdata-node";
import KeepAliveAgent from "npm:agentkeepalive";
import { AbortController as AbortControllerPolyfill } from "npm:abort-controller";
import { ReadStream as FsReadStream } from "https://deno.land/std@0.177.0/node/fs.ts";
import { type Agent } from "node:http";
import { FormDataEncoder } from "npm:form-data-encoder";
import { Readable } from "https://deno.land/std@0.177.0/node/stream.ts";
import { type RequestOptions } from "../core.ts";
import { MultipartBody } from "./MultipartBody.ts";
import { type Shims } from "./registry.ts";

// @ts-ignore (this package does not have proper export maps for this export)
import { ReadableStream } from "npm:web-streams-polyfill/dist/ponyfill.es2018.js";

type FileFromPathOptions = Omit<FilePropertyBag, "lastModified">;

let fileFromPathWarned = false;

/**
 * @deprecated use fs.createReadStream('./my/file.txt') instead
 */
async function fileFromPath(path: string): Promise<File>;
async function fileFromPath(path: string, filename?: string): Promise<File>;
async function fileFromPath(
  path: string,
  options?: FileFromPathOptions,
): Promise<File>;
async function fileFromPath(
  path: string,
  filename?: string,
  options?: FileFromPathOptions,
): Promise<File>;
async function fileFromPath(path: string, ...args: any[]): Promise<File> {
  // this import fails in environments that don't handle export maps correctly, like old versions of Jest
  const { fileFromPath: _fileFromPath } = await import(
    "formdata-node/file-from-path"
  );

  if (!fileFromPathWarned) {
    console.warn(
      `fileFromPath is deprecated; use fs.createReadStream(${
        JSON.stringify(path)
      }) instead`,
    );
    fileFromPathWarned = true;
  }
  // @ts-ignore
  return await _fileFromPath(path, ...args);
}

const defaultHttpAgent: Agent = new KeepAliveAgent({
  keepAlive: true,
  timeout: 5 * 60 * 1000,
});
const defaultHttpsAgent: Agent = new KeepAliveAgent.HttpsAgent({
  keepAlive: true,
  timeout: 5 * 60 * 1000,
});

async function getMultipartRequestOptions<T = Record<string, unknown>>(
  form: fd.FormData,
  opts: RequestOptions<T>,
): Promise<RequestOptions<T>> {
  const encoder = new FormDataEncoder(form);
  const readable = Readable.from(encoder);
  const body = new MultipartBody(readable);
  const headers = {
    ...opts.headers,
    ...encoder.headers,
    "Content-Length": encoder.contentLength,
  };

  return { ...opts, body: body as any, headers };
}

export function getRuntime(): Shims {
  // Polyfill global object if needed.
  if (typeof AbortController === "undefined") {
    // @ts-expect-error (the types are subtly different, but compatible in practice)
    globalThis.AbortController = AbortControllerPolyfill;
  }
  return {
    kind: "node",
    fetch: nf.default,
    Request: nf.Request,
    Response: nf.Response,
    Headers: nf.Headers,
    FormData: fd.FormData,
    Blob: fd.Blob,
    File: fd.File,
    ReadableStream,
    getMultipartRequestOptions,
    getDefaultAgent: (
      url: string,
    ): Agent => (url.startsWith("https")
      ? defaultHttpsAgent
      : defaultHttpAgent),
    fileFromPath,
    isFsReadStream: (value: any): value is FsReadStream =>
      value instanceof FsReadStream,
  };
}
